package com.iking.toolkit.util.minio;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Iterables;
import com.iking.toolkit.redis.RedisHashService;
import com.iking.toolkit.util.SysUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * minio存储
 *
 * @author yanglb
 * @date 2023年08月28日 10:38
 */
@Component
@Slf4j
public class MinioStorage {

    private static MinioClient minioClient;

    @Autowired
    private RedisHashService<Integer> redisHashService;

    @Value("${minio.url}")
    private String minioUrl;

    @Value("${minio.access-key}")
    private String minioAccessKey;

    @Value("${minio.secret-key}")
    private String minioSecretKey;

    @Value("${minio.bucket-name}")
    private String minioBucketName;

    /**
     * 下载地址失效时间
     */
    @Value("${download.expire.time}")
    private int downloadExpireTime;

    /**
     * 创建minio客户端
     *
     * @param
     * @return io.minio.MinioClient
     * @author yanglb
     * @date 2023/8/28 17:42
     */
    private MinioClient getMinioClient() {
        if (minioClient != null) {
            return minioClient;
        }
        synchronized (this) {
            if (minioClient != null) {
                return minioClient;
            }
            minioClient = MinioClient.builder().endpoint(minioUrl).credentials(minioAccessKey, minioSecretKey).build();
            return minioClient;
        }
    }

    /**
     * 上传远程文件
     *
     * @param multipartFile
     * @param path
     * @return com.iking.common.model.Msg
     * @author yanglb
     * @date 2023/8/28 10:54
     */
    public String uploadFile(MultipartFile multipartFile, String path, String uploadId) {
        if (multipartFile == null || StrUtil.isBlank(path)) {
            return null;
        }
        MinioClient minioClient = getMinioClient();
        try (InputStream inputStream = multipartFile.getInputStream()) {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(minioBucketName).object(path)
                    .stream(inputStream, multipartFile.getSize(), -1)
                    .contentType(contentType(FileUtil.extName(multipartFile.getOriginalFilename()))).build();
            // 文件名称相同会覆盖
//            progressBar(inputStream, uploadId, multipartFile.getSize());
            minioClient.putObject(objectArgs);
            redisHashService.put(FileUploadRedisKeyEnum.PROGRESS_SUFFIX.getKey(), uploadId, 100, 30000);
            return path;
        } catch (Exception e) {
            log.error("文件上传失败", e);
            return null;
        }
    }
    public void progressBar(InputStream inputStream, String uploadId, long totalSize) {
        new Thread(() -> {
            try {
                int num = 0;
                while (inputStream != null && inputStream.available() != 0) {
                    Thread.sleep(2000);
                    num = new BigDecimal(totalSize).subtract(new BigDecimal(inputStream.available()))
                            .divide(new BigDecimal(totalSize), 2, RoundingMode.HALF_UP)
                            .multiply(new BigDecimal(100)).intValue();
                    log.info("总大小：" + totalSize + "当前文件大小：" + inputStream.available() + "进度：" + num);
                    redisHashService.put(FileUploadRedisKeyEnum.PROGRESS_SUFFIX.getKey(), uploadId, num, 30000);
                }
            } catch (Exception e) {
                redisHashService.put(FileUploadRedisKeyEnum.PROGRESS_SUFFIX.getKey(), uploadId, 100, 30000);
                log.error("进度条计算异常：" + e.getMessage());
            }
        }).start();
    }

    /**
     * 上传本地文件
     *
     * @param inputStream
     * @param fileName
     * @param path
     * @return java.lang.String
     * @author yanglb
     * @date 2023/8/28 10:54
     */

    public String uploadFile(InputStream inputStream, long objectSize, String fileName, String path) {
        try {
            MinioClient minioClient = getMinioClient();
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(minioBucketName).object(path)
                    .stream(inputStream, objectSize, -1)
                    .contentType(contentType(FileUtil.extName(fileName))).build();
            // 文件名称相同会覆盖
            minioClient.putObject(objectArgs);
            return path;
        } catch (Exception e) {
            log.error("文件上传失败", e);
            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }



    /**
     * @param fileType
     * @return String
     * @MethodName: contentType
     * @Description: 获取文件类型
     */
    public String contentType(String fileType) {
        fileType = fileType.toLowerCase();
        String contentType = "";
        switch (fileType) {
            case "bmp":
                contentType = "image/bmp";
                break;
            case "gif":
                contentType = "image/gif";
                break;
            case "png":
            case "jpeg":
            case "jpg":
                contentType = "image/jpeg";
                break;
            case "html":
                contentType = "text/html";
                break;
            case "txt":
                contentType = "text/plain";
                break;
            case "vsd":
                contentType = "application/vnd.visio";
                break;
            case "ppt":
            case "pptx":
                contentType = "application/vnd.ms-powerpoint";
                break;
            case "doc":
            case "docx":
                contentType = "application/msword";
                break;
            case "xml":
                contentType = "text/xml";
                break;
            case "mp4":
                contentType = "video/mp4";
                break;
            default:
                contentType = "application/octet-stream";
                break;
        }
        return contentType;
    }

    /**
     * 删除文件
     *
     * @param fileUrl
     * @return com.iking.common.model.Msg
     * @author yanglb
     * @date 2023/8/28 10:54
     */

    public Boolean deleteFile(String fileUrl) {
        if (StrUtil.isBlank(fileUrl)) {
            return false;
        }
        try {
            MinioClient minioClient = getMinioClient();
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioBucketName).object(fileUrl).build());
            return true;
        } catch (Exception e) {
            log.error("删除文件异常：", e);
            return false;
        }
    }

    /**
     * 批量删除文件
     *
     * @param fileUrlList
     * @return com.iking.common.model.Msg
     * @author yanglb
     * @date 2023/8/28 10:55
     */

    public Integer deleteFileList(List<String> fileUrlList) {
        if (CollUtil.isEmpty(fileUrlList)) {
            return 0;
        }
        try {
            List<DeleteObject> deleteObjectList = new ArrayList<>();
            for (String fileUrl : fileUrlList) {
                DeleteObject deleteObject = new DeleteObject(fileUrl);
                deleteObjectList.add(deleteObject);
            }
            MinioClient minioClient = getMinioClient();
            Iterable<Result<DeleteError>> iterable = minioClient.removeObjects(
                    RemoveObjectsArgs.builder().bucket(minioBucketName).objects(deleteObjectList).build());
            for (Result<DeleteError> result : iterable) {
                log.error("====================DeleteError================= : " + result.get().message());
            }
            return fileUrlList.size() - Iterables.size(iterable);
        } catch (Exception e) {
            log.error("删除文件异常：", e);
            return 0;
        }
    }

    /**
     * 获取对象的签名下载url，有效期为10秒
     *
     * @param object
     * @return java.lang.String
     * @author yanglb
     * @date 2023/8/30 14:40
     */

    public String getDownUrl(String object) {
        try {
            MinioClient minioClient = getMinioClient();
            String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET).bucket(minioBucketName).object(object).expiry(downloadExpireTime).build());
            return url;
        } catch (Exception e) {
            log.error("获取下载地址异常: ", e);
            return null;
        }
    }

    /**
     * 拷贝文件
     *
     * @param sourceFile
     * @param targetFile
     * @return com.iking.common.model.Msg
     * @author yanglb
     * @date 2023/8/28 10:55
     */

    public String copyFile(String sourceFile, String targetFile) {
        try {
            MinioClient minioClient = getMinioClient();
            minioClient.copyObject(CopyObjectArgs.builder().bucket(minioBucketName).object(targetFile)
                    .source(CopySource.builder().bucket(minioBucketName).object(sourceFile).build()).build());
            return targetFile;
        } catch (Exception e) {
            log.error("拷贝文件失败：", e);
            return null;
        }
    }

    /**
     * 拷贝文件
     *
     * @param sourceFile
     * @param targetFile
     * @return com.iking.common.model.Msg
     * @author yanglb
     * @date 2023/8/28 10:55
     */

    public String copyFile4SourceFile(String sourceFile, String targetFile) {
        try {
            MinioClient minioClient = getMinioClient();
            String uploadPath = this.getUploadPath4SourceFile(targetFile, FileUtil.getName(sourceFile));
            minioClient.copyObject(CopyObjectArgs.builder().bucket(minioBucketName).object(uploadPath)
                    .source(CopySource.builder().bucket(minioBucketName).object(sourceFile).build()).build());
            return uploadPath;
        } catch (Exception e) {
            log.error("拷贝文件失败：", e);
            return null;
        }
    }

    /**
     * 获取上传路径
     *
     * @param picLocation
     * @param fileName
     * @return
     */
    private String getUploadPath4SourceFile(String picLocation, String fileName) {
        return (picLocation.endsWith("/")
                ? picLocation + (SysUtil.getUUID() + "." + FileUtil.extName(fileName))
                : picLocation + ("-" + SysUtil.getUUID() + "." + FileUtil.extName(fileName)));
    }

    /**
     * 下载附件到指定路径
     *
     * @param fileUrl
     * @param targetFile
     */

    public void download(String fileUrl, String targetFile) {
        if (StringUtils.isAnyEmpty(fileUrl, targetFile)) {
            return;
        }
        try {
            MinioClient minioClient = getMinioClient();
            minioClient.downloadObject(DownloadObjectArgs.builder().bucket(minioBucketName).object(fileUrl).filename(targetFile).build());
        } catch (Exception e) {
            log.error("文件下载异常：", e);
        }
    }

    /**
     * @param fileUrl
     * @description: 返回二进制文件
     * @author: myf
     * @date: 2023-11-7 11:42
     */

    public String getFileByte(String fileUrl) {
        return null;
    }

    /**
     * 获取bucket的存储容量大小
     *
     * @return
     */

    public Long getStorageSize() {
        return null;
    }

    /**
     * 批量上传文件
     *
     * @param inputStreamMap
     * @param fileNameMap
     * @param picLocation
     * @param uploadId
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @author yanglb
     * @date 2023/8/28 10:55
     */

    public Map<String, String> uploadFileBatch(Map<String, InputStream> inputStreamMap, Map<String, String> fileNameMap,
                                               String picLocation, String uploadId) {
        try {
            Map<String, String> map = new HashMap<>();
            if (CollUtil.isEmpty(inputStreamMap) || CollUtil.isEmpty(fileNameMap) || StringUtils.isAnyEmpty(picLocation, uploadId)) {
                return Collections.emptyMap();
            }
            MinioClient minioClient = getMinioClient();
            for (Map.Entry<String, InputStream> entry : inputStreamMap.entrySet()) {
                if (ObjectUtil.isNull(entry.getValue()) || ObjectUtil.isNull(fileNameMap.get(entry.getKey()))) {
                    continue;
                }
                this.uploadFileForBatch(entry, fileNameMap, picLocation, uploadId, minioClient, map);
            }
            return map;
        } catch (Exception e) {
            log.error("文件上传失败", e);
            return null;
        }
    }

    private void uploadFileForBatch(Map.Entry<String, InputStream> entry,
                                    Map<String, String> fileNameMap,
                                    String picLocation,
                                    String uploadId,
                                    MinioClient minioClient,
                                    Map<String, String> map) {
        try {
            // 获取上传路径

            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(minioBucketName).object(picLocation)
                    .stream(entry.getValue(), entry.getValue().available(), -1)
                    .contentType(contentType(FileUtil.extName(fileNameMap.get(entry.getKey())))).build();
            // 文件名称相同会覆盖
//            progressBar(entry.getValue(), uploadId, entry.getValue().available());
            minioClient.putObject(objectArgs);
            map.put(entry.getKey(), picLocation);
        } catch (Exception e) {
            log.error("上传文件失败, 文件名称:{}, 异常信息:{}", fileNameMap.get(entry.getKey()), e);
        }
    }

    /**
     * 获取上传进度条
     *
     * @param uploadId
     * @return java.lang.Integer
     * @author yanglb
     * @date 2023/9/1 9:18
     */

    public Integer getUploadProgress(String uploadId) {
        return redisHashService.get(FileUploadRedisKeyEnum.PROGRESS_SUFFIX.getKey(), uploadId);
    }
}
